Skip to content

fix(check-collections): report runner scan FAILED with logs on abort re-raise (SAS-13001)#2779

Merged
m1n0 merged 2 commits into
mainfrom
SAS-13001-scan-failure-logs
Jul 6, 2026
Merged

fix(check-collections): report runner scan FAILED with logs on abort re-raise (SAS-13001)#2779
m1n0 merged 2 commits into
mainfrom
SAS-13001-scan-failure-logs

Conversation

@m1n0

@m1n0 m1n0 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What & why

SAS-13001: a post-migration contract scan failed but produced no logs in Cloud, making it undiagnosable.

Root cause is a diagnosability hole in the engine. A single-contract (runner) scan runs execute_check_collections with abort_on_first_error=True, so the first exception during contract construction or verify re-raises immediately — before phase 3's combined upload, which is the only place that ships the engine's captured logs to Cloud and calls mark_scan_as_failed. The exception then reaches the CLI (exit 3) and the launcher only writes it to pod logs, so the Cloud scan record shows FAILED with an empty log payload. This turns any pre-upload crash into a silent, undiagnosable scan.

Fix

Before the abort_on_first_error re-raise, best-effort mark the still-PENDING runner scan FAILED with the captured logs (_report_runner_scan_failed_before_reraise). The shared Logs gatherer holds every construction/verify record (each impl is built with logs=logs), so its records are exactly what should reach Cloud. The historical re-raise contract is preserved and reporting never masks the original exception. No-op for ad-hoc runs (no scan id).

Tests

  • New test_uncaught_exception_during_verify_marks_scan_failed_with_logs: a scan that raises during verify now marks the scan FAILED with a non-empty log payload (fails on main, passes here).
  • Full soda-core unit suite: 978 passed, 2 skipped; pre-commit clean.

Note

This is the diagnosability fix. The underlying crash for users (most likely a migrated contract with an unsupported check type) is tracked separately — this change makes that (and any future pre-upload crash) self-diagnosing in Cloud.

🤖 Generated with Claude Code

…re-raise

A single-contract (runner) scan runs with abort_on_first_error=True, so the
first construction/verify exception re-raises out of execute_check_collections
before phase 3's combined upload — the only place that otherwise ships the
engine logs and marks the scan FAILED in Cloud. The exception then reaches the
CLI (exit 3) and the launcher only writes it to pod logs, leaving the Cloud
scan record FAILED with no logs and the failure undiagnosable.

Before the abort re-raise, best-effort mark the still-PENDING runner scan
FAILED with the captured logs (the shared Logs gatherer holds every
construction/verify record). The re-raise contract is preserved and reporting
never masks the original exception. No-op for ad-hoc runs (no scan id).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes a diagnosability gap for single-contract (runner) scans that run with abort_on_first_error=True: if an exception occurs during contract construction or verify(), the scan now gets marked FAILED in Soda Cloud with captured engine logs before the exception is re-raised, avoiding “FAILED with empty logs” scan records.

Changes:

  • Added best-effort pre–re-raise reporting to mark the runner scan as FAILED with captured logs when abort_on_first_error triggers.
  • Ensured reporting never masks the original exception (reporting failures are swallowed and only debug-logged).
  • Added a unit test asserting that an uncaught exception during verify() results in a sodaCoreMarkScanFailed request with a non-empty logs payload.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
soda-tests/tests/unit/test_contract_marks_scan_failed_on_connection_error.py Adds a regression test ensuring uncaught verify() exceptions still mark the runner scan FAILED and include logs.
soda-core/src/soda_core/check_collections/session.py Adds a helper invoked on abort-on-first-error paths to mark the scan FAILED with captured logs before re-raising.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Niels-b Niels-b left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. One minor comment

exc=exc if isinstance(exc, Exception) else None,
)
except Exception:
logger.debug("Could not report runner scan as failed before re-raising", exc_info=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a debug log? That doesn't show up in cloud 🤔 .

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — bumped to warning in 6030689. You're right that debug is the wrong level here: this only fires if the mark-scan-failed call itself fails, in which case the scan stays FAILED-with-no-logs in Cloud (exactly what this helper is trying to prevent), so it needs to be visible in the pod logs / Datadog. It can't go to Cloud by nature (Cloud reporting is what just failed), and warning keeps it from being drowned out while still not masking the original exception, which is re-raised regardless.

failure is undiagnosable (SAS-13001)."""
monkeypatch.setenv("SODA_SCAN_ID", "scan-under-test")

def _boom(self, *args, **kwargs):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the name 💥

"Ad-hoc run has no scan id to mark failed; mark_scan_as_failed must not be used. "
f"Requests seen: {request_types}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems there are two failure pathways here, construction and verification, but only verification is tested, did you think about testing construction as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — added test_uncaught_exception_during_construction_marks_scan_failed_with_logs in 6030689 (raises inside ContractImpl.__init__ so the exception escapes phase-1 construction rather than verify). Both abort points call the same helper, so this locks in that the construction pathway reports FAILED-with-logs too. 979 unit tests green.

Addresses review on #2779:
- The best-effort "could not report scan as failed" fallback logged at debug,
  which is easy to miss; if it fires the scan stays FAILED-with-no-logs in
  Cloud (the exact state this avoids), so bump it to warning for pod/Datadog
  visibility. Original exception is still re-raised.
- The fix reports on both abort pathways (construction + verify) but only verify
  was tested; add a construction-phase test (exception in ContractImpl.__init__).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@m1n0 m1n0 merged commit 700c9a5 into main Jul 6, 2026
22 checks passed
@m1n0 m1n0 deleted the SAS-13001-scan-failure-logs branch July 6, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants